require 'backend_interface'

class Check2 < Widget
  
  def initialize(opts = {})   
    self.height = 16
    self.margin = opts[:margin] || 2
    self.margin_right = opts[:margin_right] || 4
    self.width = 16 + self.margin_right
    
    @checked = false
    
    stack :width => 16 do
      img = image 'images/check.png', :width => 16, :height => 16
      click do
        @checked = (not @checked)
        img.path = "images/check#{'_checked' if checked?}.png"
      end
    end
    yield if block_given?
  end
  
  def checked?
    @checked
  end
end

class Visual_loader < Widget
  def initialize(opts = {})
    self.width = 32
    self.height = 32
    self.margin = opts[:margin] || 4
    
    stack :width => 32 do
      @img = image 'images/loader/loader0.gif'
    end
    
    i = 0
    @load_ani = app.animate(12) do
      i += 1
      @img.path = "images/loader/loader#{i % 12}.gif"
    end
  end
  
  def start
    @load_ani.start
  end

  def stop
    @load_ani.stop
  end
  
  def remove
    stop
    super
  end
end

class Toolbarbutton < Widget
  def initialize(opts = {}, &blk)
    self.width = 24+4
    self.height = 24
    self.margin = opts[:margin] || 4
    
    @klick = blk
 
    stack :width => 24 do # If :height is set images will flicker on visit / reload
      @img = image opts[:image], :width => 24, :height => 24
      app.click do
        if enabled?
          #set_image_state 'click'
          @klick.call unless @klick.nil?
        end
      end
      release {set_image_state 'hover' if enabled?} # Only fires if the mouse is over the button, hence hover
      hover {set_image_state 'hover' if enabled?}
      leave {set_image_state if enabled?}
    end
    
    @imgpaths = opts[:image].match(/(.*)\.(...)$/)
    self.enabled = opts[:enabled].nil? ? true : opts[:enabled]
  end
    
  def set_image_state(*state)
    @img.path = "#{@imgpaths[1]}#{"_#{state}" unless state.first.nil?}.#{@imgpaths[2]}"
  end
    
  def enabled=(value)
    @enabled = value
    set_image_state enabled? ? nil : 'disabled'
  end
    
  def enabled?
    @enabled
  end
  
  def click(&blk)
    @klick = blk
  end
end

class Toolbarseparator < Widget
  def initialize(opts = {}, &blk)
    self.width = 8+4
    self.height = 24
    self.margin = opts[:margin] || 4

    stack :width => 8 do
      @img = image 'images/separator3.png', :width => 8, :height => 24
    end
  end
end

class Frontend < Shoes

  url '/', :index
  url '/tables', :tables
  url '/tables/(\w+)', :table
  url '/tables/(\w+)/rows/(\d+)/(\w+);text/(.*)', :text_entry
  url '/tables/(\w+)/rows/(\d+)/(\w+);image/(.*)', :image_entry
  url '/tables/(\w+)/rows/(\d+)/(\w+);(.*)', :binary_entry
  
  @@back_history = Array.new
  @@forw_history = Array.new

  @@bin = BackendInterface.new
  
  def index
    # TODO: Elaborate
    visit('/tables')
  end

  def visit_with_history (path)
    @@back_history << app.location
    visit path
  end

  def toolbar
    flow :height => 33, :attach => Window, :top => 0, :left => 0 do
      background rgb(128, 128, 128)..rgb(200, 200, 200), :height => 32
      background black, :height => 1, :top => 32
  
      toolbarbutton :image => 'images/direction_left.png', :enabled => (not @@back_history.empty?) do
        unless (back = @@back_history.pop).nil?
          @@forw_history << app.location
          visit back
        end
      end

      toolbarbutton :image => 'images/direction_right.png', :enabled => (not @@forw_history.empty?) do
        unless (forw = @@forw_history.pop).nil?
          visit_with_history forw
        end
      end
      
      toolbarbutton :image => 'images/refresh3.png' do
        app.visit app.location
      end
      yield if block_given?
    end
  end

  def tables
    background rgb(232, 232, 232)
    #style(Link, :underline => false, :stroke => black)
    #style(LinkHover, :underline => true, :stroke => black)
    para strong('Tables'), :margin => 10, :margin_bottom => 0, :margin_top => 43
    stack :margin => 10 do            
      list_all_tables
    end
    
    toolbar do
      toolbarseparator
      toolbarbutton :image => 'images/document_add.png' do
        add_table
      end
      toolbarbutton :image => 'images/document_delete.png' do
        del_tables
      end
    end
  end
  
  def table (name)
    background rgb(232, 232, 232)
    
    para strong("Table #{name}"), :margin => 10, :margin_bottom => 0, :margin_top => 43
    flow :margin => 10 do      
      list_all_columns_for_table name
    end  
    
    toolbar do
      toolbarseparator
      toolbarbutton :image => 'images/document_add.png' do
        add_column name
      end
      toolbarseparator
      toolbarbutton :image => 'images/file_add.png' do
        add_row name
      end
      toolbarbutton :image => 'images/file_delete.png' do
        del_rows name
      end
    end
  end
  
  def list_all_tables
    case (resp = @@bin.list_all_tables)[:status]
      when 200 :
        @tablechecks = Array.new
        resp[:tables].each do |name|
          c = check2
          para link (name) {visit_with_history "/tables/#{name}"}, :margin => 1
          @tablechecks << [c, name]
        end
      when 401 : alert 'Access Denied'
      when 503 : alert 'Backend service not available'
      else alert resp[:status]
    end
  end

  def list_all_columns_for_table(name)    
    rows = case (resp = @@bin.list_all_rows_for_table name)[:status]
      when 200 : resp[:rows]
      when 405 : alert 'Method not allowed when list_all_columns_for_table'
      else 
        alert resp[:status]
        nil
    end
    
    case (resp = @@bin.list_all_columns_for_table name)[:status]
      when 200:
        col_width = "#{100 / resp[:columns].length}%"
        @rowchecks = Array.new
        resp[:columns].each do |column|
          stack :margin => 0, :width => col_width do
            # Column name and type
            para strong(column[:name]), ' ', column[:type], :margin => 0
            # Column mime
            para em(column[:mime]), :margin => [0, 0, 0, 10]
            
            # Rows for Column
            rows.each do |row|            
              unless column[:name] == 'id'
                link_name = case column[:mime]
                              when /text\/.*/ : 'read'
                              when /image\/.*/ : 'show'
                              else 'download'
                            end
                para link(link_name) {visit_with_history "/tables/#{name}/rows/#{row}/#{column[:name]}\;#{column[:mime]}"}, :margin => 1
              else
                # Read ID directly                
                c = check2
                case (resp = @@bin.read_row_entry_for_table name, :row => row, :column_name => 'id', :column_mime => column[:mime])[:status]
                  when 200 : para resp[:entry], :margin => 1
                  else para em("n/a (#{resp[:status]})"), :margin => 1
                end
                @rowchecks << [c, row]
              end
            end
          end
        end
      else alert resp[:status]
    end unless rows.nil?
  end

  def add_table
    table_name = ask 'Name of the table to add'
    
    case (resp = @@bin.add_table table_name)[:status]
      when 201 : visit app.location
      when 409 : alert "Table #{table_name} already exist"
      else alert resp[:status]
    end unless table_name.nil?
  end

  def del_tables
    selected = @tablechecks.map { |c, name| name if c.checked? }.compact unless @tablechecks.nil?
    if selected.nil? or selected.empty?
      alert 'No tables selected for deletion'
    else
      selected.each do |table_name|  
        case (resp = @@bin.del_table table_name)[:status]
          when 204 : visit app.location
          else alert resp[:status]
        end
      end if confirm "Do you really want to delete table(s) #{selected.join(', ')}"
    end
  end

  def add_column (table_name)
    dialog :width => 230, :height => 220, :title => "#{table_name} - Add Column" do
      background rgb(232, 232, 232)
      
      stack :margin => 10, :width => '100%' do
        para 'Column name:', :margin_bottom => 0
        col_name = edit_line :width => '100%', :margin_right => 10
        col_name.focus # seems not to work on osx
        
        para 'Column type:', :margin_bottom => 0
        col_type = list_box :items => ['Integer', 'Decimal', 'Text', 'Blob']
        
        para 'Column mime-type:', :margin_bottom => 0
        col_mime = edit_line :width => '100%', :margin_right => 10
        
        flow :margin_top => 10 do
          button 'Cancel' do close end
          button 'Add' do
            if col_name.text.empty? or col_mime.text.empty?
              alert 'Please enter name and mime type'
            else
              case (resp = @@bin.add_column_for_table table_name, :name => col_name.text, :type => col_type.text, :mime => col_mime.text)[:status]
                when 201 : 
                  app.owner.visit "/tables/#{table_name}" #app.owner.location if app.owner.location == "/tables/#{table_name}"
                  close
                when 409 : alert "Column #{col_name.text} already exist"
                else alert resp[:status]
              end
            end
          end
        end
      end
    end
  end

  def add_row (table_name)
    case status = @@bin.add_row_for_table(table_name)[:status]
      when 201 : visit app.location
      else alert status
    end if confirm "Add new row to table #{table_name}?"    
  end
  
  def del_rows (table_name)
    selected = @rowchecks.collect {|c, id| id if c.checked? }.compact
    if selected.empty?
      alert 'No rows selected for deletion'
    else
      selected.each do |row_id|
        case status = @@bin.del_row_for_table(table_name, :id => row_id)[:status]
          when 204 : visit app.location
          else alert status
        end
      end if confirm "Do you really want to delete row(s) #{selected.join(', ')}"
    end
  end

  def text_entry (table_name, row_id, col_name, text_type)
    background rgb(232, 232, 232)
    
    resp = @@bin.read_row_entry_for_table table_name, :row => row_id, :column_name => col_name, :column_mime => "text/#{text_type}"
    
    stack :margin => 10, :margin_top => 42, :height => '100%' do
      case resp[:status]
        when 200, 204 : @textbox = edit_box resp[:entry] ? resp[:entry] : '', :width => '100%', :height => '100%'
        when 404 : para app.location[/(.*);/, 1], ': ', strong('Not Found'), :margin_bottom => 4
        else alert "Hmm bad: #{resp[:status]}"
      end
    end
    
    toolbar do
      toolbarseparator
      toolbarbutton :image => 'images/download.png', :enabled => (resp[:status] == 200)  do
        app.clipboard =  @textbox.text
        alert 'Text sent to clipboard'
      end
      toolbarbutton :image => 'images/upload.png', :enabled => [200, 204].include?(resp[:status]) do
        resp = @@bin.write_row_entry_for_table(table_name, :row => row_id, :column_name => col_name, :column_mime => "text/#{text_type}", :entry => @textbox.text) if confirm 'Upload text?'
        case resp[:status].to_f
          when 201 : app.visit app.location
          else alert "Could not upload new text: #{resp[:status]}"
        end
      end
    end
  end

  def image_entry(table_name, row_id, col_name, image_type)
    download_entry table_name, row_id, col_name, "image/#{image_type}" do |slot, entry|
      img_filename = ".cache/#{table_name}_#{row_id}_#{col_name}_#{rand}.#{image_type}"
      img_file = File.new(img_filename, "w+b") # b necessary for Windows
      img_file.write @resp[:entry]
      img_file.close
      slot.append { image img_filename }
    end
  end

  def binary_entry(table_name, row_id, col_name, col_mime)
    download_entry table_name, row_id, col_name, col_mime do |slot, entry|
      slot.append { para em 'Binary content' }
    end
  end

  def download_entry (table_name, row_id, col_name, col_mime)    
    background rgb(232, 232, 232)
    s = stack :margin => 10, :margin_top => 42 do      
      vis_load = visual_loader
      @t = Thread.new do
        Thread.stop        
        @resp = @@bin.read_row_entry_for_table table_name, :row => row_id, :column_name => col_name, :column_mime => col_mime
        vis_load.remove        
        case @resp[:status]
        when 200 :
            @dl_button.enabled = true
            @ul_button.enabled = true
            yield(s, @resp[:entry]) if block_given?            
        when 204 :
            s.append {para app.location[/(.*);/, 1], ': ', strong('No Content'), :margin => 0}
            @ul_button.enabled = true
        when 404 : 
            s.append {para app.location[/(.*);/, 1], ': ', strong('Not Found'), :margin => 0}
        end  
      end
    end

    toolbar do
      toolbarseparator
      @dl_button = toolbarbutton :image => 'images/download.png', :enabled => false  do
        unless (file_name = ask_save_file).nil?
          file_name += (".#{col_mime[/\/(.*)/, 1]}" unless file_name =~ /.#{col_mime[/\/(.*)$/, 1]}$/) if col_mime =~ /^image\//
          file = File.new file_name, 'wb'
          file.write @resp[:entry]
          file.close
        end
      end
      @ul_button = toolbarbutton :image => 'images/upload.png', :enabled => false  do
        unless (file_name = ask_open_file).nil?
          file = File.open file_name, 'rb'
          write_resp = @@bin.write_row_entry_for_table table_name, :row => row_id, :column_name => col_name, :entry => file.read
          file.close
          
          case write_resp[:status].to_f
            when 201 : app.visit app.location
            else alert "Could not upload : #{write_resp[:status]}"
          end
        end
      end
    end
    
    # Spinlock
    until @t.stop?
    end
    @t.run
  end
end

Shoes.app :width => 400, :height => 480, :title => 'Frontend'
